7.5. 批量规范化

要点
  • 批量规范化对数据进行标准化后再缩放,缩放的参数是一个可学习的参数,目的是为了控制神经元的值不会很大
  • 批量规范化可以加速模型训练,但不能提高模型精度
  • 批量规范化层和 dropout 一样,在训练模式和预测模式下计算不同。
  • 批量规范化层的副作用会有正则化效果(后续论文指出等价于加入一些噪音),没必要和 dropout 同时使用

4.8 数值稳定性和模型初始化中,我们研究了保持神经元的值不能太大,也不能太小,对数值稳定性有很大的作用,对于一个深度网络,控制神经元的值可以加速网络训练,这启发我们在每一层对网络数据进行规范化,把数据强行限制在一个范围内,保证数值稳定性。

注意

批量规范化的英文是 batch normalization,统计学的 normalization 指的是归一化(均值为 0,方差为 1),这里是借用这个概念,批经过归一化后再缩放。

1. 批量归一化

μB=1|B|iBxi and σB2=1|B|iB(xiμB)2+ϵ

然后再做额外的调整(可学习的参数)

xi+1=γxiμBσB+β

这样每个神经元的均值为 β,方差为 γ

注意
  • 这里 均值 β,方差为 γ 是可学习的参数,目的就是让神经元的值限制在一个范围内
  • 这里的 ϵ 是防止方差为 0 导致除以 0 无法计算

2. 批量规范化层

批量规范化层可以用在

2.1 对于全连接层

7.5. 批量规范化.png 对于全连接层,用在每个特征维,上图表示用在输入之前

2.2 对于卷积层

7.5. 批量规范化-3.png 对于卷积层,用在每个通道上,上图同一个通道的所有像素点用的是同一个 βγ, 上图表示作用在卷积层后

3. 代码实现

3.1 手动实现 BN

import torch
from torch import nn
from d2l import torch as d2l

def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum):
    # 通过is_grad_enabled来判断当前模式是训练模式还是预测模式
    if not torch.is_grad_enabled():
        # 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差
        X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
    else:
        assert len(X.shape) in (2, 4) # 这里只考虑全连接层与二维卷积的情况
        if len(X.shape) == 2:
            # 使用全连接层的情况,计算特征维上的均值和方差
            mean = X.mean(dim=0) # 沿着y轴求平均
            var = ((X - mean) ** 2).mean(dim=0)
        else:
            # 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。
            # 这里我们需要保持X的形状以便后面可以做广播运算
            mean = X.mean(dim=(0, 2, 3), keepdim=True) # 保持通道数不变(1轴),沿着其余方向求平均
            var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True)
        # 训练模式下,用当前的均值和方差做标准化
        X_hat = (X - mean) / torch.sqrt(var + eps)
        # 更新移动平均的均值和方差
        moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
        moving_var = momentum * moving_var + (1.0 - momentum) * var
    Y = gamma * X_hat + beta  # 缩放和移位
    return Y, moving_mean.data, moving_var.data
  • 第 19 行:对于卷积的情况,保持通道数不变,其余方向求平均
  • 利用移动平均来计算全局的平均和方差,用于预测,预测一个样本的时候,计算中批的平均值就是经过当前层的全局平均值(不是整体样本的平均值)
class BatchNorm(nn.Module):
    # num_features:完全连接层的输出数量或卷积层的输出通道数。
    # num_dims:2表示完全连接层,4表示卷积层
    def __init__(self, num_features, num_dims):
        super().__init__()
        if num_dims == 2:
            shape = (1, num_features) # BN 层参数的维度,全连接层(批大小,特征个数)
        else:
            shape = (1, num_features, 1, 1) # BN 层参数的维度(批大小,通道数,长,高)
        # 参与求梯度和迭代的拉伸和偏移参数,分别初始化成1和0
        self.gamma = nn.Parameter(torch.ones(shape))
        self.beta = nn.Parameter(torch.zeros(shape))
        # 非模型参数的变量初始化为0和1
        self.moving_mean = torch.zeros(shape)
        self.moving_var = torch.ones(shape)

    def forward(self, X):
        # 如果X不在内存上,将moving_mean和moving_var
        # 复制到X所在显存上
        if self.moving_mean.device != X.device:
            self.moving_mean = self.moving_mean.to(X.device)
            self.moving_var = self.moving_var.to(X.device)
        # 保存更新过的moving_mean和moving_var
        Y, self.moving_mean, self.moving_var = batch_norm(
            X, self.gamma, self.beta, self.moving_mean,
            self.moving_var, eps=1e-5, momentum=0.9)
        return Y

利用 6.6 卷积神经网络(LeNet) 加入 BatchNorm 层观察训练效果

net = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5), BatchNorm(6, num_dims=4), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
    nn.Linear(16*4*4, 120), BatchNorm(120, num_dims=2), nn.Sigmoid(),
    nn.Linear(120, 84), BatchNorm(84, num_dims=2), nn.Sigmoid(),
    nn.Linear(84, 10))


lr, num_epochs, batch_size = 1.0, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
loss 0.273, train acc 0.899, test acc 0.807
32293.9 examples/sec on cuda:0

7.5. 批量规范化-5.png|center|400

6.6 卷积神经网络(LeNet)#^353f07 比 loss 下降的更快

3.2 简洁实现 BN

net = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5), nn.BatchNorm2d(6), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2),
    nn.Conv2d(6, 16, kernel_size=5), nn.BatchNorm2d(16), nn.Sigmoid(),
    nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
    nn.Linear(256, 120), nn.BatchNorm1d(120), nn.Sigmoid(),
    nn.Linear(120, 84), nn.BatchNorm1d(84), nn.Sigmoid(),
    nn.Linear(84, 10))

内置的 BatchNorm 对输入的张量维度没有要求,参数大小就是上一层输出维度(或者通道数),实际上对于多维张量,里面的维度始终是(批的大小,通道数,图像维度1,图像维度2,图像维度3,...),始终都可以用 dim != 1 来求平均,dim = 1 的维度就是参数的维度

参考文献



© 2023 yanghn. All rights reserved. Powered by Obsidian